package server;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.List;

import exceptions.ServerMsgException;
import exceptions.WrongStateException;

public class Server{
	private ServerState state = ServerState.WaitingPlayers;
	
	private Gambler gambler = Gambler.newGambler();
	private ServerSocket myServerSocket = null;
	private Socket[] mySocket = new Socket[nPlayers];
	private int port = 2501;
	private BufferedReader[] input = new BufferedReader[nPlayers];
	private PrintStream[] output = new PrintStream[nPlayers];
	
	// These declarations could be done in the gambler!

	private static final int nPlayers = 4;
		// The Number of Players
	private int connectedPlayers =0;
		// the number of player already connected to the server;
	private int startingPlayer=0;
		// this value indicates which player starts 
	String cardsOfPlayer[] = new String[nPlayers]; 
	
	
	
	
	
	//----------------------------------------//
	
	public static void main(String[] args){
		Server myServer = new Server();
		
		// Infinity Loop
		do {
			try {
				
				////////////////////////
				// State: waitingPlayers
				////////////////////////
				
				switch(myServer.state){
				case WaitingPlayers:
					if (myServer.myServerSocket==null) myServer.myServerSocket = new ServerSocket(myServer.port);
					
					for(int i = myServer.connectedPlayers; i<nPlayers; i++){
						myServer.mySocket[i] = myServer.myServerSocket.accept();
						myServer.input[i] = new BufferedReader(new InputStreamReader(myServer.mySocket[i].getInputStream()));
						myServer.output[i] = new PrintStream(myServer.mySocket[i].getOutputStream()); 
						
						String entryMsg = myServer.input[i].readLine();
						
						if(entryMsg.equals(PlayerMessages.CONNECTED)){
							myServer.sendAcceptedMessage(i);
							myServer.sendMessage(i,ServerMessages.YOUAREPLAYER+i);
						}
					}
					myServer.changeState(ServerState.Initialize);
					break;
				
				///////////////////////
				// State: Initialize	
				///////////////////////
				
				case Initialize:
					
					myServer.gambler.reFillCards();
					myServer.gambler.shuffle();				
					
					for(int i = 0; i<nPlayers; i++){
						myServer.sendMessage(i,ServerMessages.STARTINGTHEGAME);
							// sends a message to player to indicate the the game has started.
						
						List<Card> cardsToSend = myServer.gambler.getCards(13);
							// Get thirteen cards 
						
						String outcomingCards = myServer.gambler.getCardParser().inputParseCards(cardsToSend);
							// Turns the cards into a string to send as message to a player. 
						
						myServer.sendMessage(i,outcomingCards);
							// Send the card-coded message to a player.
					}
					myServer.changeState(ServerState.WaitingThreeCards);
					break;
				
				///////////////////////////
				// State: WaitingThreeCards
				///////////////////////////
				
				case WaitingThreeCards:
					for(int i = 0; i<nPlayers; i++){
						String entryMsg = myServer.input[i].readLine();
							// get the cards for a player 
						myServer.sendAcceptedMessage(i);
							// Send the 'accepted' message.
						myServer.store3CardsFromPlayers(entryMsg,i);
							// saves the card-coded message 
						
					}
					
					myServer.deal3CardsToEachPlayer();
						// sends the cards to other players.
						// Modifies the stored cards in the server lists.
					
					myServer.changeState(ServerState.Round);
				    break;

				///////////////
				// State: Round
				///////////////
				
				case Round:
/*					//ACA ESPERAR NOTIFICACION DE JUGADOR PARA EMPEZAR

					// select the first player to play.
					if (myServer.lastRoundLoser>=0) {
							// Looks for the loser of last round
						myServer.startingPlayer =myServer.lastRoundLoser;
					} else {
						Card TwoClubs = new Card(CardSuits.CLUBS, 2);
						for (int i=0;i<nPlayers;i++) {
							// Looks for the player who has the 2 of clubs card.
							
							if (myServer.cardsOnPlayer.get(i).contains(TwoClubs)) {
								myServer.startingPlayer = i;
								i = nPlayers;
							}
						}
					}
*/					
					String playedCard="";
						// This String will contain the first card played by any player
					Boolean firstPlayerChosen=false;
						// If a player send a card, firstPLayerChosen will be true. 
					
					for (int i=0;i<nPlayers;i++) {
						
							String entryMsg = myServer.input[i].readLine();

							
							if(!firstPlayerChosen && !entryMsg.equals(PlayerMessages.NOCARD)) {
								firstPlayerChosen=true;
								myServer.startingPlayer = i;
								playedCard = entryMsg;
							}
							myServer.sendAcceptedMessage(i);
					}
					
					myServer.sendMessageToAll(playedCard);
						// sends the first card to everyone
						
					// from now, the server directs the playing order in the game.
					for (int i=1;i<nPlayers;i++) {
						
						myServer.sendMessage(((i+myServer.startingPlayer) % 4), ServerMessages.PLAY);
							// Gives the order to each player to start the game

						String oneCard = myServer.input[(i+myServer.startingPlayer) % 4].readLine();
							// Waits a card for a player.
						
						myServer.sendAcceptedMessage((i+myServer.startingPlayer) % 4);
							// Responds to player that message was receipt OK.

						myServer.sendMessageToAll(oneCard);
							// Send the card to all players.						
					}
					
					myServer.changeState(ServerState.ExpectingResults);
				    break;

				///////////////////////////
				// State: ExpectingResults				
				///////////////////////////
				
				case ExpectingResults:
					// All players must compute the same results.
					// The Decision is up to player 0.
					//for (int i=0;i<nPlayers;i++) {
						
						String entryMsg = myServer.input[0].readLine();
						myServer.sendAcceptedMessage(0);
						if(entryMsg.equals(PlayerMessages.GAMEOVER)) {
							myServer.changeState(ServerState.FinnishingTheMatch);
						} else 
						if(entryMsg.equals(PlayerMessages.NEXTROUND)) { 
							myServer.changeState(ServerState.Round);
						} else 
						if (entryMsg.equals(PlayerMessages.NEXTHAND)) {
							myServer.changeState(ServerState.Initialize);
						}
					//}
/*					// This State has to accomplish many steps. Here are shown, without code. 
					// Step One: Check the card on the table that loses the round.
					// Step Two: Count points to be added by cards on the table
					// Step Three: actualize scores on the server & send results of Round to players
					// Step four: Check if there are any round left to play.
						// if Any, go to play the next round
						// inform to players to continue playing.
					// Step Five: Check if any play had more point than maxScore.
						// if true, inform player that the game is over, send final scores.
						// if no, start another hand & inform to players to continue playing.

					
					// Step One: Check the card on the table that loses the round.

					Card max = myServer.cardsOnTable.get(0);
						// max will contain the greatest card on table,  
						// initialized with the first Card on table, thrown by the startingPlayer.
					
					int maxPos = myServer.startingPlayer;
						// maxPos will contain the number of the player that threw the greatest card

					for (int i=1; i<nPlayers; i++) {
						Card card = myServer.cardsOnTable.get(i);
						if (card.greaterOfTheSameSuit(max)) {
							max = card;
							maxPos = (myServer.startingPlayer + i) % nPlayers;
						}
					}
					
					
					
					// Step Two: Count points to be added by cards on the table
					
					int points=0;
					for (Card card: myServer.cardsOnTable) {
						if (card.suit==CardSuits.HEARTS) points++;
					}
					
					
					
					// Step Three: actualize scores on the server & send results of Round to players
					
					myServer.scores[maxPos]=points+myServer.scores[maxPos];
					
					for (int i=0;i<nPlayers;i++) {
						myServer.sendMessage(i, "Results Loser " + maxPos + " "+points);
							// Sends the player that loses the round and the points to be added.
					}					

					
					
					// Step four: Check if there are any round left to play.
						myServer.roundsLeft--;
						
						if(myServer.roundsLeft>0) {
							myServer.changeState("Round");
								// if Any, go to play the next round
							for (int i=0;i<nPlayers;i++) {
								myServer.sendMessage(i, "NextRound");
									// inform to players to continue playing.
							}
						} else {

							
						// Step Five: Check if any play had more point than maxScore.
						
							boolean gameOver=false;
							for (int i=0;i<nPlayers;i++) {
								gameOver = gameOver || (myServer.scores[i]>myServer.maxScore); 
							}
						
							if (gameOver) {
								// if true, inform player that the game is over, send final scores.
								myServer.changeState("ExpectContinuing");								
								for (int i=0;i<nPlayers;i++) {
									myServer.sendMessage(i, "GameOver");
								}
							} else {
								// if no, start another hand & inform to players to continue playing.
								
								myServer.changeState("Initialize");
								
								for (int i=0;i<nPlayers;i++) {
									myServer.sendMessage(i, "NextHand"); 
								}								
							}
						}
*/
					break;
				
				
				////////////////////////////////
				// State: 'Finnishing the Match'
				////////////////////////////////
				
				case FinnishingTheMatch:
					// TERMINAR EL JUEGO
					// Waits the response of every player for play another game or quit.
					
					// 1 - Read the decision of each player to play another game or not.
						boolean[] cont = new boolean[nPlayers];
						for (int i=0;i<nPlayers;i++) {
							entryMsg = myServer.input[i].readLine();
							myServer.sendAcceptedMessage(i);
							if (entryMsg.equals(PlayerMessages.NEXTHAND)) {
								cont[i]=true;
							} else cont[i]=false;
						}
						
					// 2 - Those player who wants to continue playing must change their number id.
						
						int j=0;
						for (int i=0;i<nPlayers;i++) {
							if (cont[i]) {
								if (j<i) {
									myServer.input[j] = myServer.input[i];
									myServer.output[j] = myServer.output[i];
									myServer.mySocket[j] = myServer.mySocket[i];


									myServer.input[i] = null;
									myServer.output[i] = null;
									myServer.mySocket[i] = null;
									
								}
								j++;
							} else {
								myServer.input[i] = null;
								myServer.output[i] = null;
								myServer.mySocket[i] = null;
							}
						}
						
						myServer.connectedPlayers = j;
						
						for (int i=0;i<myServer.connectedPlayers;i++) {
							myServer.sendMessage(i, ServerMessages.YOUAREPLAYER + i);
						}
						
					
					// 3 - Change State to WaitingPlayers
						
						myServer.changeState(ServerState.WaitingPlayers);
				break;
				default: throw new WrongStateException("The Server has a wrong state");
				}
				
			} catch (IOException e) {
				e.printStackTrace();
			}
			  catch (WrongStateException e){
				  e.printStackTrace();
			  }
		} while (true);
	}
	
	
	// PRIVATE METHODS
	
	/**
	 * Sends a Message to a player and expects confirmation response from the player
	 * .
	 * @param player 
	 * @param message
	 */
	private void sendMessage(int player, String message) {
		this.output[player].println(message);
		try {
			String response = this.input[player].readLine();
			if(!response.equals(PlayerMessages.ACCEPTED)) { throw new ServerMsgException("Unexpected response Message: "+ response);};
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ServerMsgException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Default Answer for every message received. 
	 * @param player
	 */
	private void sendAcceptedMessage(int player) {
		this.output[player].println(ServerMessages.ACCEPTED);
	}
	
	/**
	 * sets a new state in the sever.
	 * @param newState
	 */
	private void changeState(ServerState newState){
		this.state = newState;
	}
	
	/**
	 * Temporarily stores the three cards received from players at the beginning of the game.  
	 * @param cards
	 * @param player
	 */
	private void store3CardsFromPlayers(String cards, int player){
		if (player>=0 && player<nPlayers) this.cardsOfPlayer[player] = cards;
		else System.out.println("Error al recibir 3 cartas de jugador "+player);
	}
	
	/**
	 * Send the same message to every player.
	 * @param aMessage
	 */
	private void sendMessageToAll(String aMessage){
		for (int i=0;i<nPlayers;i++) {
			this.sendMessage(i,aMessage);}
	}
	
	/**
	 * Sends the three card from any player to the next player.
	 */
	private void deal3CardsToEachPlayer(){
		// Me parecio mejor tener este metodo que tener uno solo (y mas complejo) que 
		//reciba las cartas y las reparta. 
		
		for (int i=0;i<nPlayers;i++) {

			this.sendMessage(i, cardsOfPlayer[(i+1)%nPlayers]);
				// sends the message to the player indicating the new cards
			
			
//			List<Card> c = gambler.getCardParser().outputParseCards(cardsOfPlayer[(i+1)%nPlayers]);
//				// get the cards corresponding to the message sent
//			for (Card card : c) {
//				cardsOnPlayer.get((i+1)%nPlayers).remove(card);
//					// removes those cards from a player in the server 
//				cardsOnPlayer.get(i).add(card);
//				// adds those cards to other player in the server
//			}
			
			
		}
	}

}
